/*
    An tokenizer for Dialog resource parser
    Copyright (C) 2006 Sergey Shishmintzev
    Derived from SQLite sources.

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Contributor(s):
      Hwaci http://www.hwaci.org/
      Sergey Shishmintzev <sergey.shishmintzev@gmail.com>

*/

/*
** 2001 September 15
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** An tokenizer for SQL
**
** This file contains C code that splits an SQL input string up into
** individual tokens and sends those tokens one-by-one over to the
** parser for analysis.
**
** _Id: tokenize.c,v 1.116 2006/01/20 17:56:33 drh Exp _
*/

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "dialogresInt.h"
#include <parse.h>

/* An array to map all upper-case characters into their corresponding
** lower-case character. 
*/
static const unsigned char UpperToLower[] = {
      0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
     18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
     36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
     54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103,
    104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,
    122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,
    108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,
    126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
    144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,
    162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,
    180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,
    198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
    216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
    234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
    252,253,254,255
};

/*
** The keywordCode function looks up an identifier to determine if
** it is a keyword.  If it is a keyword, the token code of that keyword is 
** returned.  If the input is not a keyword, TK_ID is returned.
**
** The implementation of this routine was generated by a program,
** mkkeywordhash.h, located in the tool subdirectory of the distribution.
** The output of the mkkeywordhash.c program is written into a file
** named keywordhash.h and then included into this source file by
** the #include below.
*/
#include "keywordhash.h"

/*
** If X is a character that can be used in an identifier and
** X&0x80==0 then IsIdChar[X] will be 1.  If X&0x80==0x80 then
** X is always an identifier character.  (Hence all UTF-8
** characters can be part of an identifier).  IsIdChar[X] will
** be 0 for every character in the lower 128 ASCII characters
** that cannot be used as part of an identifier.
**
** In this implementation, an identifier can be a string of
** alphabetic characters, digits, and "_" plus any character
** with the high-order bit set.  The latter rule means that
** any sequence of UTF-8 characters or characters taken from
** an extended ISO8859 character set can form an identifier.
*/
static const char IsIdChar[] = {
/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
    0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /* 2x */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,  /* 3x */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 4x */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,  /* 5x */
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* 6x */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,  /* 7x */
};

#define IdChar(C)  (((c=C)&0x80)!=0 || (c>0x1f && IsIdChar[c-0x20]))

#ifdef _MSC_VER
#pragma warning (disable: 4028)
#endif 

#ifdef isdigit
#undef isdigit
#endif
static __inline int isdigit(char c)
{
   switch (c)
   {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        return 1;
   }
   return 0;
}

#ifdef isxdigit
#undef isxdigit
#endif
static __inline int isxdigit(char c)
{
   switch (c)
   {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
      case 'A': case 'a':
      case 'B': case 'b':
      case 'C': case 'c':
      case 'D': case 'd':
      case 'E': case 'e':
      case 'F': case 'f':
        return 1;
   }
   return 0;
}

#ifdef isspace
#undef isspace
#endif
static __inline int isspace(char c)
{
   switch (c)
   {
      case 0x09:
      case 0x0A:
      case 0x0B:
      case 0x0C:
      case 0x0D:
      case 0x20:
        return 1;
   }
   return 0;
}

/*
** Return the length of the token that begins at z[0]. 
** Store the token type in *tokenType before returning.
*/
static int getToken(const unsigned char *z, int *tokenType, unsigned *nLineNo,  const unsigned char **zLastLine){
  int i, c;
  switch( *z ){
    case '\n': 
      (*nLineNo)++;
      *zLastLine=z+1;
      if (**zLastLine=='\r') (*zLastLine)++;
    case ' ': case '\t': case '\f': case '\r': {
      for(i=1; (c=z[i]) && isspace(c); i++)
      {
        if (c=='\n') 
        {
          (*nLineNo)++;
          *zLastLine=&z[i+1];
          if (**zLastLine=='\r') (*zLastLine)++;
        } 
      }
      *tokenType = TK_SPACE;
      return i;
    }
    case ';': {
      for(i=1; (c=z[i])!=0 && c!='\n'; i++){
      }
      *tokenType = TK_COMMENT;
      return i;
    }
    case '(': {
      *tokenType = TK_LP;
      return 1;
    }
    case ')': {
      *tokenType = TK_RP;
      return 1;
    }
    case '{': {
      *tokenType = TK_LB;
      return 1;
    }
    case '}': {
      *tokenType = TK_RB;
      return 1;
    }
    /*case ';': {
      *tokenType = TK_SEMI;
      return 1;
    }*/
    case '+': {
      *tokenType = TK_PLUS;
      return 1;
    }
    case '-': {
      *tokenType = TK_MINUS;
      return 1;
    }
    case '*': {
      *tokenType = TK_STAR;
      return 1;
    }
    case '/': {
      if( z[1]!='*' || z[2]==0 ){
        *tokenType = TK_SLASH;
        return 1;
      }
      for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){
        if (c=='\n') 
        {
          (*nLineNo)++;
          *zLastLine=&z[i+1];
          if (**zLastLine=='\r') (*zLastLine)++;
        } 
      }
      if( c ) i++;
      *tokenType = TK_COMMENT;
      return i;
    }
    case '%': {
      *tokenType = TK_REM;
      return 1;
    }
    case '=': {
      *tokenType = TK_EQ;
      return 1 + (z[1]=='=');
    }
    case '<': {
      if( (c=z[1])=='=' ){
        *tokenType = TK_LE;
        return 2;
      }else if( c=='>' ){
        *tokenType = TK_NE;
        return 2;
      }else if( c=='<' ){
        *tokenType = TK_LSHIFT;
        return 2;
      }else{
        *tokenType = TK_LT;
        return 1;
      }
    }
    case '>': {
      if( (c=z[1])=='=' ){
        *tokenType = TK_GE;
        return 2;
      }else if( c=='>' ){
        *tokenType = TK_RSHIFT;
        return 2;
      }else{
        *tokenType = TK_GT;
        return 1;
      }
    }
    case '!': {
      if( z[1]!='=' ){
        *tokenType = TK_ILLEGAL;
        return 2;
      }else{
        *tokenType = TK_NE;
        return 2;
      }
    }
    case '|': {
      if( z[1]!='|' ){
        *tokenType = TK_BITOR;
        return 1;
      }else{
        *tokenType = TK_OR;
        return 2;
      }
    }
    case ',': {
      *tokenType = TK_COMMA;
      return 1;
    }
    case '&': {
      *tokenType = TK_BITAND;
      return 1;
    }
    case '~': {
      *tokenType = TK_BITNOT;
      return 1;
    }
    case '`':
    case '\'':
    case '"': {
      int delim = z[0];
      for(i=1; (c=z[i])!=0; i++){
        if( c==delim ){
          if( z[i+1]==delim ){
            i++;
          }else{
            break;
          }
        }
        else if (c=='\n') 
        {
          (*nLineNo)++;
          *zLastLine=&z[i+1];
          if (**zLastLine=='\r') (*zLastLine)++;
        } 
      }
      if( c ){
        *tokenType = TK_STRING;
        return i+1;
      }else{
        *tokenType = TK_ILLEGAL;
        return i;
      }
    }
#if 0 
    case '.': 
#endif
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9': {
      *tokenType = TK_INTEGER;
      if (z[1]=='x') for(i=2; isxdigit(z[i]); i++){}
      else           for(i=0; isdigit(z[i]); i++){}
#if 0
      if( z[i]=='.' ){
        i++;
        while( isdigit(z[i]) ){ i++; }
        *tokenType = TK_FLOAT;
      }
      if( (z[i]=='e' || z[i]=='E') &&
           ( isdigit(z[i+1]) 
            || ((z[i+1]=='+' || z[i+1]=='-') && isdigit(z[i+2]))
           )
      ){
        i += 2;
        while( isdigit(z[i]) ){ i++; }
        *tokenType = TK_FLOAT;
      }
#endif
      return i;
    }
    case '$':
    case '@': {
      int n = 0;
      *tokenType = TK_VARIABLE;
      for(i=1; (c=z[i])!=0; i++){
        if( IdChar(c) ){
          n++;
        }else if( c=='(' && n>0 ){
          do{
            i++;
          }while( (c=z[i])!=0 && !isspace(c) && c!=')' );
          if( c==')' ){
            i++;
          }else{
            *tokenType = TK_ILLEGAL;
          }
          break;
        }else if( c==':' && z[i+1]==':' ){
          i++;
        }else{
          break;
        }
      }
      if( n==0 ) *tokenType = TK_ILLEGAL;
      return i;
    }
    default: {
      if( !IdChar(*z) ){
        break;
      }
      for(i=1; IdChar(z[i]); i++){}
      *tokenType = keywordCode((char*)z, i);
      return i;
    }
  }
  *tokenType = TK_ILLEGAL;
  return 1;
}

int dialogresRunParser(Parse *pParse, const char *Stmt, char **pzErrMsg){
  int i;
  void *pEngine;
  int tokenType;
  int lastTokenParsed = -1;

  assert(pParse);
  pParse->rc = dialogres_Ok;
  i = 0;
  pEngine = dialogresParserAlloc(__dialogres_malloc);
  if( pEngine==0 ){
    return pParse->rc = dialogres_NoMemory;
  }
  //pParse->Tail = pParse->Stmt = Stmt;
  pParse->zLastLine = Stmt;
  pParse->nLineNo = 1;
  while( Stmt[i]!=0 ){
    //assert( i>=0 );
    pParse->sLastToken.z = &Stmt[i];
    //assert( pParse->sLastToken.dyn==0 );
    pParse->sLastToken.pos = pParse->sLastToken.z - pParse->zLastLine + 1;
    pParse->sLastToken.line = pParse->nLineNo;
    pParse->sLastToken.n = getToken((unsigned char*)&Stmt[i],&tokenType,&pParse->nLineNo,(const unsigned char**)&pParse->zLastLine);
    i += pParse->sLastToken.n;
    switch( tokenType ){
      case TK_SPACE:
      case TK_COMMENT: break;
      case TK_ILLEGAL: {
        if ( pParse->rc==dialogres_Ok )
        {
          pParse->sErrToken = pParse->sLastToken;
          pParse->rc = dialogres_IllegalToken;
        }
        goto abort_parse;
      }
      case TK_SEMI: {
        //pParse->Tail = &Stmt[i];
        /* Fall thru into the default case */
      }
      default: {
        dialogresParser(pEngine, tokenType, pParse->sLastToken, pParse);
        lastTokenParsed = tokenType;
        if( pParse->rc!=dialogres_Ok ){
          goto abort_parse;
        }
        break;
      }
    }
  }
abort_parse:
  if( Stmt[i]==0 && pParse->rc==dialogres_Ok ){
    /*if( lastTokenParsed!=TK_SEMI ){
      DialogResourceParser(pEngine, TK_SEMI, pParse->sLastToken, pParse);
      pParse->zTail = &zSql[i];
    }*/
    dialogresParser(pEngine, 0, pParse->sLastToken, pParse);
  }
  dialogresParserFree(pEngine, __dialogres_free);
  return pParse->rc;
}
